/*----------------------------------------------------------------------------------------------
 *
 * This file is XIU's property. It contains XIU's trade secret, proprietary and
 * confidential information.
 *
 * The information and code contained in this file is only for authorized XIU employees
 * to design, create, modify, or review.
 *
 * DO NOT DISTRIBUTE, DO NOT DUPLICATE OR TRANSMIT IN ANY FORM WITHOUT PROPER AUTHORIZATION.
 *
 * If you are not an intended recipient of this file, you must not copy, distribute, modify,
 * or take any action in reliance on it.
 *
 * If you have received this file in error, please immediately notify XIU and
 * permanently delete the original and any copy of any file and any printout thereof.
 * (c) www.xiusdk.cn
 *---------------------------------------------------------------------------------------------*/

package com.samples;

import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.Camera;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.Log;

import com.inartar.effect.Constraints;
import com.utils.Accelerometer;
import com.xiusdk.ynfacetrack.YNFace;
import com.xiusdk.ynfacetrack.YNFaceTrack;

import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.LinkedList;
import java.util.Queue;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.opengles.GL10;

import jp.co.cyberagent.android.gpuimage.GPUImageFilter;
import jp.co.cyberagent.android.gpuimage.util.TextureRotationUtil;

public class VideoRenderer implements GLSurfaceView.Renderer, Camera.PreviewCallback{

	public interface Callback
	{
		void onSurfaceCreated();
		void onSurfaceChanged();
		void onDestorySurface();
		int onPreviewFrame(byte data[], int width, int height, int orientation, boolean frontFacing, YNFace[] faces);
	}

	public enum ScaleType { CENTER_INSIDE, CENTER_CROP }

	private static final String TAG = "VideoRenderer";
	private static boolean DEBUG = true;//0;

	public VideoRenderer(Context context, Callback callback, GLSurfaceView glSurfaceView) {

		glSurfaceView.setEGLContextFactory(new DefaultContextFactory());
		glSurfaceView.setRenderer(this);
		glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
		mGLSurfaceView = new WeakReference<GLSurfaceView>(glSurfaceView);

		mTracker = new YNFaceTrack(context, 0);

		mCallback = new WeakReference<Callback>(callback);
		mRunOnDraw = new LinkedList<Runnable>();
		this.mContext = context;

		mGLCubeBuffer = ByteBuffer.allocateDirect(CUBE.length * 4)
				.order(ByteOrder.nativeOrder())
				.asFloatBuffer();
		mGLCubeBuffer.put(CUBE).position(0);

		mGLPreviewTextureBuffer = ByteBuffer.allocateDirect(TextureRotationUtil.TEXTURE_NO_ROTATION.length * 4)
				.order(ByteOrder.nativeOrder())
				.asFloatBuffer();
		mGLPreviewTextureBuffer.clear();
		mGLPreviewTextureBuffer.put(TextureRotationUtil.TEXTURE_NO_ROTATION).position(0);
	}

	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		if( mTracker != null )
		{
			mTracker.destory();
			mTracker = null;
		}
	}

	@Override
	public void onSurfaceCreated(GL10 arg0,
								 javax.microedition.khronos.egl.EGLConfig arg1) {
		mRendererThread = Thread.currentThread();
		if( mCallback != null)
		{
			mCallback.get().onSurfaceCreated();
		}
	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height) {

		mOutputWidth = width;
		mOutputHeight = height;

		if(mFilter.isInitialized())
		{
			mFilter.destroy();
		}
		mFilter.init();
		mFilter.onOutputSizeChanged(width, height);

		if( mCallback != null)
		{
			mCallback.get().onSurfaceChanged();
		}
	}

	@SuppressLint("WrongCall")
	@Override
	public void onDrawFrame(GL10 gl) {
		synchronized(mRunOnDraw)
		{
			while(!mRunOnDraw.isEmpty())
			{
				mRunOnDraw.poll().run();
			}
		}
		GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
		GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

		if( mVideoFrame != null) {
			int texture = mCallback.get().onPreviewFrame(mVideoFrame.frame, mVideoFrame.width, mVideoFrame.height, mOrientation, mFrontFacing, mVideoFrame.faces);
			if (texture > 0) {
				mFilter.onDraw(texture, mGLCubeBuffer, mGLPreviewTextureBuffer);
			}
		}
	}

	@Override
	public void onPreviewFrame(final byte[] data, final Camera camera) {

		final Camera.Size previewSize = camera.getParameters().getPreviewSize();

		/**
		 * 获取重力传感器返回的方向
		 */
		int dir = Accelerometer.getDirection();

		/**
		 * 请注意前置摄像头与后置摄像头旋转定义不同
		 * 请注意不同手机摄像头旋转定义不同
		 */
		if (mFrontFacing &&
				((mOrientation == Constraints.YN_CLOCKWISE_ROTATE_270 && (dir & 1) == 1) ||
						(mOrientation == Constraints.YN_CLOCKWISE_ROTATE_90 && (dir & 1) == 0)))
			dir = (dir ^ 2);

		final YNFace[] faces =
				mTracker != null ?
						mTracker.track(data, 0, previewSize.width, previewSize.height, previewSize.width, dir)
						: null;

//		if( faces != null) {
//			for (YNFace face : faces) {
//				for (int i = 0; i < face.ptsSize; i++) {
//					int x = (int) face.pts[i * 2];
//					int y = (int) face.pts[i * 2 + 1];
//					Log.d("Test", "x: " + x + "   y: " + y);
////					for (int k = -3; k < 3; k++) {
////						int m = x + k;
////						if (m >= 0 && m < previewSize.width) {
////							for (int l = -3; l < 3; l++) {
////								int n = y + l;
////								if (n >= 0 && n < previewSize.height) {
////									data[n * previewSize.width + m] = (byte) 0xFF;
////								}
////							}
////						}
////					}
//				}
//			}
//		}

		runOnDraw(new Runnable() {

			@Override
			public void run() {

				if (mImageWidth != previewSize.width || mImageHeight != previewSize.height) {
					mImageWidth = previewSize.width;
					mImageHeight = previewSize.height;
					adjustImageScaling();
				}

				byte[] newData = null;

				if (mVideoFrame == null || mVideoFrame.width != previewSize.width || mVideoFrame.height != previewSize.height)
				{
					mVideoFrame = new VideoFrame();
					mVideoFrame.width = previewSize.width;
					mVideoFrame.height = previewSize.height;
					mVideoFrame.frame = new byte[previewSize.width * previewSize.height * 3 / 2];
				}

				mVideoFrame.faces = faces;
				newData = mVideoFrame.frame;
				mVideoFrame.frame = data;

				camera.addCallbackBuffer(newData);
			}
		});

		mGLSurfaceView.get().requestRender();
	}

	public void runOnDraw(final Runnable runnable) {
		if (runnable != null) {
			if (mRendererThread == Thread.currentThread()) {
				runnable.run();
			} else {
				synchronized (mRunOnDraw) {
					mRunOnDraw.add(runnable);
				}
			}
		}
	}

	public void setupCamera(final int orientation, final boolean frontFacing) {
		runOnDraw(new Runnable() {
			@Override
			public void run() {
				if (mFrontFacing != frontFacing || mOrientation != orientation) {
					mOrientation = orientation;
					mFrontFacing = frontFacing;
					adjustImageScaling();
				}
			}
		});
	}

	private void trace()
	{
		long timer = System.currentTimeMillis();

		if( mTimeCounter == 0)
			mTimeCounter = timer;

		long fps = (timer - mTimeCounter) / ++mStart;

		if (mStart > 100) {

			mTimeCounter = timer;
			mStart = 0;

			Log.i(TAG, "fps:" + fps);
		}
	}

	private void adjustImageScaling() {
		float outputWidth = mOutputWidth;
		float outputHeight = mOutputHeight;

		float[] cube = CUBE;
		float[] previewTextureCoords = TextureRotationUtil.TEXTURE_NO_ROTATION;

		if( outputWidth > 0. && outputHeight > 0. && mImageHeight > 0 && mImageWidth > 0)
		{
			float ratioWidth = 1.0f;
			float ratioHeight = 1.0f;

			int im = mImageWidth;
			int ih = mImageHeight;

			//90 or 270 degree.
			if (( mOrientation & 1 ) == 1) {
				im = mImageHeight;
				ih = mImageWidth;
			}

			float image_width = outputWidth;
			float image_height = outputHeight;
			if( outputWidth*ih > im*outputHeight)
			{
				image_height =  outputWidth*ih / im;
			}
			else if( outputWidth*ih < im*outputHeight)
			{
				image_width = im*outputHeight  / ih;
			}

			if (mScaleType == ScaleType.CENTER_CROP) {
				ratioWidth = image_width/outputWidth;
				ratioHeight = image_height/outputHeight;
			} else {
				ratioWidth = outputWidth/image_width;
				ratioHeight = outputHeight/image_height;
			}

			cube = new float[]{
					CUBE[0] * ratioWidth, CUBE[1] * ratioHeight,
					CUBE[2] * ratioWidth, CUBE[3] * ratioHeight,
					CUBE[4] * ratioWidth, CUBE[5] * ratioHeight,
					CUBE[6] * ratioWidth, CUBE[7] * ratioHeight,
			};
		}

		mGLCubeBuffer.clear();
		mGLCubeBuffer.put(cube).position(0);

		mGLPreviewTextureBuffer.clear();
		mGLPreviewTextureBuffer.put(previewTextureCoords).position(0);

		Log.d(TAG, String.format("adjustImageScaling: %f,%f,%f,%f,%f,%f,%f,%f",
				previewTextureCoords[0], previewTextureCoords[1], previewTextureCoords[2], previewTextureCoords[3],
				previewTextureCoords[4], previewTextureCoords[5], previewTextureCoords[6], previewTextureCoords[7]));
	}

	private class DefaultContextFactory implements GLSurfaceView.EGLContextFactory {
		private int EGL_CONTEXT_CLIENT_VERSION = 0x3098;

		public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
			int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,
					EGL10.EGL_NONE };
			return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
					mEGLContextClientVersion != 0 ? attrib_list : null);
		}

		public void destroyContext(EGL10 egl, EGLDisplay display,
								   EGLContext context) {

			if( mCallback != null)
			{
				mCallback.get().onDestorySurface();
			}

			if (!egl.eglDestroyContext(display, context)) {
				Log.e("DefaultContextFactory", "display:" + display + " context: " + context);
			}
		}
	}

	static final float CUBE[] = {
			-1.0f, -1.0f,
			1.0f, -1.0f,
			-1.0f, 1.0f,
			1.0f, 1.0f,
	};
	private class VideoFrame
	{
		int width;
		int height;
		YNFace[] faces;
		byte[] frame;
	}

	private final Queue<Runnable> mRunOnDraw;
	private Context mContext = null;
	private int mEGLContextClientVersion = 2;
	private WeakReference<GLSurfaceView> mGLSurfaceView;
	private Thread mRendererThread;

	private int mStart = 0;
	private long mTimeCounter = 0;
	private boolean mFrontFacing = false;
	private int mOrientation = 0;
	private GPUImageFilter mFilter = new GPUImageFilter();
	private final FloatBuffer mGLCubeBuffer;
	private final FloatBuffer mGLPreviewTextureBuffer;
	private int mOutputWidth;
	private int mOutputHeight;
	private int mImageWidth;
	private int mImageHeight;
	private ScaleType mScaleType = ScaleType.CENTER_CROP;
	private WeakReference<Callback> mCallback = null;
	private YNFaceTrack mTracker = null;

	private VideoFrame mVideoFrame = null;

}
